package com.share.system.api.service;

import cn.hutool.core.date.DateUtil;
import com.share.system.api.domain.Region;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * todo (后续改进)大量的redis操作都没有加入事务，存在不一致风险（原子性问题）
 * todo 优化: 存在缓存不一致问题，区域父子级关系，层级结构，失效之后无法即使更新（原子性问题）
 */
@Service
@Slf4j
public class RegionRedisService {

    @Autowired
    @Qualifier("stringRedisTemplate")
    private RedisTemplate<String, String> redisTemplate;

    // Redis key 前缀定义
    public static final String REGION_INFO_PREFIX = "region:info:";           // 区域信息 key: region:info:{code}
    public static final String REGION_CHILDREN_PREFIX = "region:children:";   // 子区域列表 key: region:children:{parentCode}
    public static final String REGION_HIERARCHY_PREFIX = "region:hierarchy:"; // 层级结构 key: region:hierarchy:{level}


    /**
     * 使用Lua脚本进行原子性版本比较和更新
     */
    private static final String UPDATE_WITH_VERSION_CHECK_LUA =
            "local current_version = redis.call('HGET', KEYS[1], 'version') " +
                    "local new_version = ARGV[1] " +
                    "if current_version == false or tonumber(new_version) > tonumber(current_version) then " +
                    "    for i = 2, #ARGV, 2 do " +
                    "        redis.call('HSET', KEYS[1], ARGV[i], ARGV[i+1]) " +
                    "    end " +
                    "    return 1 " +
                    "else " +
                    "    return 0 " +
                    "end";
    /**
     * lua脚本脚本类
     */
    private final DefaultRedisScript<Long> updateWithVersionCheckScript = new DefaultRedisScript<>();

    @PostConstruct
    public void init() {
        updateWithVersionCheckScript.setScriptText(UPDATE_WITH_VERSION_CHECK_LUA);
        updateWithVersionCheckScript.setResultType(Long.class);
    }

//    public RegionRedisService(RedisTemplate<String, String> redisTemplate) {
//        this.redisTemplate = redisTemplate;
//    }

    /**
     * 构建区域信息Map
     */
    private Map<String, String> buildRegionInfoMap(Region region) {
        Map<String, String> regionInfo = new HashMap<>();
        regionInfo.put("id", String.valueOf(region.getId()));
        regionInfo.put("code", region.getCode());
        regionInfo.put("name", region.getName());
        regionInfo.put("parentCode", region.getParentCode());
        regionInfo.put("level", String.valueOf(region.getLevel()));
        regionInfo.put("createTime", String.valueOf(region.getCreateTime()));
        regionInfo.put("createBy", region.getCreateBy());
        regionInfo.put("updateBy", region.getUpdateBy());
        regionInfo.put("updateTime", String.valueOf(region.getUpdateTime()));
        regionInfo.put("delFlag", region.getDelFlag());
        regionInfo.put("remark", region.getRemark());
        if (region.getVersion() != null){
            regionInfo.put("version", String.valueOf(region.getVersion()));
        }
        return regionInfo;
    }

    /**
     * 保存区域信息到Redis
     * 1. 保存区域详细信息
     * 2. 维护父子关系
     * 3. 按层级分类存储
     * todo 优化: 使用Redis事务进行批量操作
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean saveRegion(Region region) {
        // 1. 保存区域详细信息 (Hash结构)
        String regionInfoKey = REGION_INFO_PREFIX + region.getCode();
        // 2. 维护父子关系 (Set结构存储子区域code)
        // 首先获取旧的父级code，用于清理旧的父子关系
        Object oldParentCodeObj = redisTemplate.opsForHash().get(regionInfoKey, "parentCode");
        System.out.println("旧的父级code: " + oldParentCodeObj);
        String oldParentCode = oldParentCodeObj != null ? (String) oldParentCodeObj : null;

        // 如果父级code发生变化，需要清理旧的父子关系
        if (!Objects.equals(oldParentCode, region.getParentCode()) && oldParentCode != null) {
                String oldChildrenKey = REGION_CHILDREN_PREFIX + oldParentCode;
                redisTemplate.opsForSet().remove(oldChildrenKey, region.getCode());
        }
        String childrenKey = REGION_CHILDREN_PREFIX + region.getParentCode();
        redisTemplate.opsForSet().add(childrenKey, region.getCode());

        Map<String, String> regionInfo = buildRegionInfoMap(region);
        /**
         * 删除之前的区域父子关系，重新新增父子关系
         */
        redisTemplate.opsForHash().putAll(regionInfoKey, regionInfo);

        // 3. 按层级分类存储 (Set结构)
        String hierarchyKey = REGION_HIERARCHY_PREFIX + region.getLevel();
        redisTemplate.opsForSet().add(hierarchyKey, region.getCode());
        log.info("保存区域信息到Redis: {} - {}", region.getCode(), region.getName());
        return true;
    }

    /**
     * 根据code获取区域信息
     */
    public Region getRegionByCode(String code) {
        String regionInfoKey = REGION_INFO_PREFIX + code;
        Map<Object, Object> regionData = redisTemplate.opsForHash().entries(regionInfoKey);

        if (regionData.isEmpty()) {
            return null;
        }

        Region region = new Region();
        region.setId(Long.valueOf((String) regionData.get("id")));
        region.setCode((String) regionData.get("code"));
        region.setName((String) regionData.get("name"));
        region.setParentCode((String) regionData.get("parentCode"));
        region.setLevel(Long.valueOf((String) regionData.get("level")));
        region.setCreateTime(DateUtil.parse((String) regionData.get("createTime")));
        region.setUpdateTime(DateUtil.parse((String) regionData.get("updateTime")));
        region.setDelFlag((String) regionData.get("delFlag"));

        return region;
    }

    /**
     * 获取指定区域的所有子区域
     */
    public Set<String> getChildrenCodes(String parentCode) {
        String childrenKey = REGION_CHILDREN_PREFIX + parentCode;
        return redisTemplate.opsForSet().members(childrenKey);
    }

    /**
     * 获取指定层级的所有区域代码
     */
    public Set<String> getCodesByLevel(String level) {
        String hierarchyKey = REGION_HIERARCHY_PREFIX + level;
        return redisTemplate.opsForSet().members(hierarchyKey);
    }

    /**
     * 批量保存区域信息
     * 使用Redis Pipeline进行批量操作，批量取出数据更新日期进行比对
     *  如果时间大于数据库时间，则不进行更新
     * @param regions
     * @return
     */
    public int batchSaveRegionsWithPipeline(List<Region> regions) {
        if (regions == null || regions.isEmpty()){
            return 0;
        }
        List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Region region : regions) {
                String regionInfoKey = REGION_INFO_PREFIX + region.getCode();
                // 先获取Redis中的更新时间
                connection.hGet(regionInfoKey.getBytes(), "updateTime".getBytes());
            }
            return null;
        });
        // 根据获取到的时间判断是否需要更新
        List<Region> regionsToUpdate = new ArrayList<>();
        for (int i = 0; i < regions.size(); i++) {
            Region region = regions.get(i);
            Object redisUpdateTimeObj = results.get(i);
            // 如果Redis中没有数据或者MySQL数据更新，则需要更新
            if (redisUpdateTimeObj == null) {
                regionsToUpdate.add(region);
            } else {
                byte[] redisUpdateTimeBytes;
                if (redisUpdateTimeObj instanceof String) {
                    redisUpdateTimeBytes = ((String) redisUpdateTimeObj).getBytes();
                } else if (redisUpdateTimeObj instanceof byte[]) {
                    redisUpdateTimeBytes = (byte[]) redisUpdateTimeObj;
                } else {
                    // 处理其他意外类型，直接更新
                    regionsToUpdate.add(region);
                    continue;
                }

                if (isRegionNewer(region, redisUpdateTimeBytes)) {
                    regionsToUpdate.add(region);
                }
            }
        }
        // 执行批
        // 执行批量更新
        return executeBatchUpdate(regionsToUpdate);
    }


    /**
     * 获取完整的省市区层级结构 (省->市->区)
     * 返回 Map<String, Map<String, List<String>>> 结构
     */
    public Map<String, Map<String, List<String>>> getFullHierarchyStructure() {
        Map<String, Map<String, List<String>>> result = new HashMap<>();

        // 获取所有省级区域
        Set<String> provinceCodes = getCodesByLevel("1");

        for (String provinceCode : provinceCodes) {
            Region province = getRegionByCode(provinceCode);
            if (province != null && "0".equals(province.getDelFlag())) {
                Map<String, List<String>> cityDistrictMap = new HashMap<>();

                // 获取该省的所有市级区域
                Set<String> cityCodes = getChildrenCodes(provinceCode);
                for (String cityCode : cityCodes) {
                    Region city = getRegionByCode(cityCode);
                    if (city != null && "0".equals(city.getDelFlag())) {
                        // 获取该市的所有区县级区域
                        Set<String> districtCodes = getChildrenCodes(cityCode);
                        List<String> districtNames = new ArrayList<>();

                        for (String districtCode : districtCodes) {
                            Region district = getRegionByCode(districtCode);
                            if (district != null && "0".equals(district.getDelFlag())) {
                                districtNames.add(district.getName());
                            }
                        }

                        cityDistrictMap.put(city.getName(), districtNames);
                    }
                }

                result.put(province.getName(), cityDistrictMap);
            }
        }

        return result;
    }

    /**
     * 根据省份名称获取该省的市和区数据
     */
    public Map<String, List<String>> getProvinceHierarchy(String provinceName) {
        // 先通过名称找到省份code
        Set<String> provinceCodes = getCodesByLevel("1");

        for (String provinceCode : provinceCodes) {
            Region province = getRegionByCode(provinceCode);
            if (province != null && provinceName.equals(province.getName()) && "0".equals(province.getDelFlag())) {
                Map<String, List<String>> cityDistrictMap = new HashMap<>();

                // 获取该省的所有市级区域
                Set<String> cityCodes = getChildrenCodes(provinceCode);
                for (String cityCode : cityCodes) {
                    Region city = getRegionByCode(cityCode);
                    if (city != null && "0".equals(city.getDelFlag())) {
                        // 获取该市的所有区县级区域
                        Set<String> districtCodes = getChildrenCodes(cityCode);
                        List<String> districtNames = new ArrayList<>();

                        for (String districtCode : districtCodes) {
                            Region district = getRegionByCode(districtCode);
                            if (district != null && "0".equals(district.getDelFlag())) {
                                districtNames.add(district.getName());
                            }
                        }

                        cityDistrictMap.put(city.getName(), districtNames);
                    }
                }

                return cityDistrictMap;
            }
        }

        return new HashMap<>();
    }



    /**
     * 判断区域数据是否比Redis中的更新
     */
    private boolean isRegionNewer(Region region, byte[] redisUpdateTimeBytes) {
        if (redisUpdateTimeBytes == null || redisUpdateTimeBytes.length == 0) {
            return true;
        }
        try {
            String redisUpdateTimeStr = new String(redisUpdateTimeBytes);
            Date redisUpdateTime = DateUtil.parse(redisUpdateTimeStr);
            return region.getUpdateTime().after(redisUpdateTime);
        } catch (Exception e) {
            log.warn("解析Redis时间失败，强制更新: {}", e.getMessage());
            return true;
        }
    }

    /**
     * 批量更新redis中的区域数据
     * @param regionsToUpdate
     * @return 更新的数量
     */
    private int executeBatchUpdate(List<Region> regionsToUpdate) {
        if (regionsToUpdate.isEmpty()) {
            return 0;
        }

        List<Long> results = new ArrayList<>();

        /**
         * 准备lua脚本中需要的参数，版本号和对象键值对，lua脚本对版本进行检查
         */
        for (Region region : regionsToUpdate) {
            String regionInfoKey = REGION_INFO_PREFIX + region.getCode();
            Map<String, String> regionInfo = buildRegionInfoMap(region);

            // 准备 Lua 脚本参数
            List<String> keys = Collections.singletonList(regionInfoKey);
            List<String> args = new ArrayList<>();

            // 添加版本号作为第一个参数
            args.add(region.getVersion() != null ? String.valueOf(region.getVersion()) : "0");

            // 添加键值对参数
            for (Map.Entry<String, String> entry : regionInfo.entrySet()) {
                args.add(entry.getKey());
                args.add(entry.getValue());
            }
            // 执行 Lua 脚本
            Long result = redisTemplate.execute(updateWithVersionCheckScript, keys, args.toArray(new String[0]));
            results.add(result);
        }

        // 统计成功更新的数量
        int updatedCount = 0;
        for (Long result : results) {
            if (result != null && result == 1) {
                updatedCount++;
            }
        }

        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Region region : regionsToUpdate) {
                String childrenKey = REGION_CHILDREN_PREFIX + region.getParentCode();
                connection.sAdd(childrenKey.getBytes(), region.getCode().getBytes());
                // 为子区域键设置24小时过期时间
                connection.expire(childrenKey.getBytes(), 86400);
                // 按层级分类存储
                String hierarchyKey = REGION_HIERARCHY_PREFIX + region.getLevel();
                connection.sAdd(hierarchyKey.getBytes(), region.getCode().getBytes());
                // 为层级键设置24小时过期时间
                connection.expire(hierarchyKey.getBytes(), 86400);
            }
            return null;
        });

        log.info("批量更新了 {} 个区域信息到Redis", updatedCount);
        return updatedCount;
    }


}
